#include <iostream>
#include <algorithm>
#include <fstream>
#include <string>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>

#include "defines.hpp"
#ifdef BSD_SENDFILE
#include <sys/uio.h>
#else
#include <sys/sendfile.h>
#endif
#include "Listener.hpp"
#include "Request.hpp"

#define REQUEST_DEBUG 0
#define TIMER_DEBUG 0
#define PRINT_DEBUG(x) std::cout << boost::this_thread::get_id() << ": " << x << std::endl;

Request::Request(io_service &ior) : io(ior), sock(ior), keepalive_timer(ior), st(ior) {
	docroot="/store/www/forsakens";
}

Request::~Request() {
		sock.cancel();
		sock.close();
}

void Request::start(const boost::system::error_code& error) {
	if( error ) {
		finish( boost::system::error_code() );
		return;
	}
	if( REQUEST_DEBUG ) PRINT_DEBUG("Connect");
	setup_timer();
	async_read_until( sock, req_data,
		std::string("\r\n\r\n"),
		st.wrap(
			boost::bind(&Request::handle_request_headers, this,
				boost::asio::placeholders::error,
				boost::asio::placeholders::bytes_transferred)
		)
		);
}
void Request::setup_timer() {
	// Setup keepalive timeout
	if( TIMER_DEBUG ) PRINT_DEBUG("timer expires_from_now");
	keepalive_timer.expires_from_now(boost::posix_time::seconds(20));
	if( TIMER_DEBUG ) PRINT_DEBUG("timer async_wait");
	keepalive_timer.async_wait( st.wrap(
		boost::bind(&Request::finish, this, boost::asio::placeholders::error)
		));
	if( TIMER_DEBUG ) PRINT_DEBUG("timer setup_timer done");
}

void Request::handle_request_headers(const boost::system::error_code& error, size_t bytes_transferred) {
	keepalive_timer.cancel();
	if( error == boost::asio::error::eof ) {
		// A disconnect. Just to point thats eof
		finish( boost::system::error_code() );
		return;
	}else if( error ) {
		// Other error. Disconnect.
		finish( boost::system::error_code() );
		return;
	}
	// 'req_data' must contain received headers
	std::istream is(&req_data);
	std::string s;
	while( is.good() && !is.eof() ) {
		// Read headers line by line
		getline(is, s);

		size_t cmdend = s.find(" ");
		if( cmdend == std::string::npos ) {
			// Smth wrong
			continue;
		}
		std::string command;
		std::transform(s.begin(), s.begin()+cmdend, std::back_inserter<std::string>(command), toupper);

		if( command == "GET") {
				uri = s.substr(4);
				size_t uriend = uri.find(" ");
				if( uriend != std::string::npos ) {
					uri.erase( uriend );
					if( uri == "/" ) uri="/index.html";
				}else{
					uri = "";
				}
		} else if( command == "HOST:" ) {
				host = s.substr(cmdend+1);
		} else if( command == "STOP" ) {
				// Just for testing
				io.stop();
				return;
		}//if command
	}//while !eof

	// Check we are ready to process request and process it
	if( !uri.empty() && !host.empty() ) {
		int ret;
		std::string path = docroot;
		path += uri;
		if( (ret = stat( path.c_str(), &filestat))!=0 ) {
			answer_code(404);
		}else{
			// File exists
			std::ostream os(&resp_data);
			os
				<< "HTTP/1.1 200 OK\r\n"
				<< "Content-Length: " << filestat.st_size << "\r\n"
				<< "Last-Modified: Sat, 21 Nov 2009 21:19:42 GMT\r\n"
				<< "\r\n";
			// Write headers, other will be written in wr_sendfile
			async_write(sock, resp_data, st.wrap(
					boost::bind(&Request::wr_sendfile, this, path, boost::asio::placeholders::error)
				));
 		}
	}else{
		answer_code(500);
	}
}

void Request::finish(const boost::system::error_code& error) {
	if( TIMER_DEBUG ) PRINT_DEBUG("request::finish");
	if( error != boost::asio::error::operation_aborted ) {
		if( REQUEST_DEBUG || TIMER_DEBUG ) PRINT_DEBUG("Reaping Request");
		delete this;
	}
}

void Request::answer_code(int code) {
	std::ostream os(&resp_data);
	os << "HTTP/1.1 " << code << "\r\n\r\n";
	writeanswer();
}

void Request::writeanswer() {
	async_write(sock, resp_data, st.wrap(
			boost::bind(&Request::start, this, boost::asio::placeholders::error)
		));
}

void Request::wr_sendfile(std::string path, const boost::system::error_code& error) {
	if( error ) {
		finish( boost::system::error_code() );
		return;
	}
	FILE *fp = fopen(path.c_str(), "r");
	if( !fp ) {
		finish( boost::system::error_code() );
		return;
	}
	off_t ofs=0, sent=0;
	while( 1 ) {
#ifdef BSD_SENDFILE
		int r = sendfile( fileno(fp), sock.native_handle(), ofs, 0, NULL, &sent, SF_SYNC);
#else
		int r = sendfile( sock.native(), fileno(fp), &ofs, 0);
#endif
		if( r==0 ) break; // ok
		if( errno!=EAGAIN ) break; //some giant fail
		ofs += sent;
	}
	fclose(fp);
	if( REQUEST_DEBUG ) PRINT_DEBUG("file served via sendfila");
	start( boost::system::error_code() );
}
